﻿Imports System.Reflection
Imports System.Text.RegularExpressions

''' <summary>
''' 親列属性を作成します
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class ParentRelationColumnAttributeFactory

    Private Shared _factory As ParentRelationColumnAttributeFactory = Nothing

    ''' <summary>
    ''' インスタンスします
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>DBMSによって、適切なクラスがインスタンスされます</remarks>
    Public Shared Function Instance() As ParentRelationColumnAttributeFactory
        '#97　MySQL対応
        'DBMSごとの生成クラスを統合。
        If _factory Is Nothing Then
            _factory = New ParentRelationColumnAttributeFactory
        End If
        Return _factory
    End Function

    ''' <summary>
    ''' 型から親列属性を生成します
    ''' </summary>
    ''' <param name="atr">作業対象テーブル</param>
    ''' <remarks></remarks>
    Public Function Create(ByVal atr As TableAttribute) As List(Of ParentRelationColumnAttribute)
        Dim cols As New List(Of ParentRelationColumnAttribute)

        '型がルート、または、独自のプロパティがターゲット
        Dim fn As Predicate(Of PropertyInfo) =
            Function(prop)
                If atr.IsRootBase Then Return True
                If prop.DeclaringType.Equals(atr.ModelType) Then Return True
                Return False
            End Function

        '親列
        For Each item In (From el In atr.ModelType.GetProperties Where fn.Invoke(el) AndAlso PropertyReader.IsParent(el) = True)
            If item.PropertyType.IsInterface Then
                Dim msg = "#73　親プロパティの型をインターフェイスにすることはできません。Ignore属性を付けてマッピング対象から外してください。（クラス：{0}、プロパティ：{1}、型：{2}）"
                Throw New NotSupportedException(String.Format(msg, item.DeclaringType.Name, item.Name, item.PropertyType.Name))
            End If

            For Each subItem In (From el In CreateMain(item) Select DirectCast(el, ColumnAttribute))
                cols.Add(subItem)
            Next
        Next

        Return cols
    End Function

    ''' <summary>
    ''' プロパティから親列属性を作成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateMain(ByVal prop As PropertyInfo) As IEnumerable(Of ParentRelationColumnAttribute)
        '指定属性
        Dim manuals As List(Of ParentRelationColumnAttribute) = CreateAsManual(prop)

        If manuals IsNot Nothing AndAlso manuals.Any Then Return manuals

        '制約上属性
        Try
            Dim masters As IEnumerable(Of ParentRelationColumnAttribute) = CreateAsMaster(prop)
            Return masters
        Catch ex As Exception
            Throw New InvalidOperationException(String.Format("制約上の親関連属性を自動作成できませんでした。（型：{0}、プロパティ：{1}）", prop.DeclaringType.Name, prop.Name), ex)
        End Try
    End Function

    ''' <summary>
    ''' 指定上の属性を生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateAsManual(ByVal prop As PropertyInfo) As IEnumerable(Of ParentRelationColumnAttribute)
        Dim atrs As New List(Of ParentRelationColumnAttribute)

        '指定された関連属性でループ
        '#71「テーブル作成クエリの列の定義順序改善」
        For Each item As RelationAttribute In (From el In AttributeReader.Read(Of RelationAttribute)(prop).Reverse)
            '#101 生成されるSQL文にエスケープ文字を追加する
            Dim atr As ParentRelationColumnAttribute = ParentRelationColumnAttribute.CreateInstanceFromRelation(prop, item)
            atrs.Add(atr)
        Next

        Return atrs
    End Function

    ''' <summary>
    ''' 制約上の属性を生成します
    ''' </summary>
    ''' <param name="prop"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CreateAsMaster(ByVal prop As PropertyInfo) As IEnumerable(Of ParentRelationColumnAttribute)
        If prop.DeclaringType.Equals(prop.PropertyType) Then Throw New InvalidOperationException("自己参照の場合、手動で指定する必要があります。")

        Dim atrs As New List(Of ParentRelationColumnAttribute)

        '相手の主キーでループ。
        For Each item As ColumnAttribute In (From el In prop.PropertyType.ToTable.Columns Where el.IsLoadKey)
            '#101 生成されるSQL文にエスケープ文字を追加する
            Dim atr As ParentRelationColumnAttribute = ParentRelationColumnAttribute.CreateInstanceFromColumn(prop, item)
            atrs.Add(atr)
        Next

        Return atrs
    End Function

End Class
